package main

import (
	"bytes"
	"fmt"
	"io"
	"math"
	"math/rand"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	SERVER_NETWORK="tcp"
	SERVER_ADDRESS="127.0.0.1:8085"
	DELIMITER='\t'
)

var wg sync.WaitGroup

func printLog(role string, sn int, format string, args ...interface{}) {
	if !strings.HasSuffix(format,"\n"){
		format+="\n"
	}
	fmt.Printf("%s[%d]:%s",role,sn,fmt.Sprintf(format,args...))
}

func printServerLog(format string, args ...interface{}) {
	printLog("Server",0,format,args...)
}

func printClientLog(sn int, format string, args ...interface{}) {
	printLog("Client",sn,format,args...)
}

func strToInt32(str string) (int32, error) {
	num,err:=strconv.ParseInt(str,10,0)
	if err!=nil{
		return 0,fmt.Errorf("\"%s\" is not integer",str)
	}
	if num>math.MaxInt32 || num<math.MinInt32{
		return 0,fmt.Errorf("%d is not 32-bit integer",num)
	}
	return int32(num),nil
}

func cbrt(param int32) float64 {
	return math.Cbrt(float64(param))
}

func read(conn net.Conn) (string, error) {
	readBytes:=make([]byte,1)
	var buffer bytes.Buffer
	for {
		_,err:=conn.Read(readBytes)
		if err!=nil{
			return "",err
		}
		readByte:=readBytes[0]
		if readByte==DELIMITER{
			break
		}
		buffer.WriteByte(readByte)
	}
	return buffer.String(),nil
}

func write(conn net.Conn, content string) (int, error) {
	var buffer bytes.Buffer
	buffer.WriteString(content)
	buffer.WriteByte(DELIMITER)
	return conn.Write(buffer.Bytes())
}

func serverGo() {
	var listener net.Listener
	listener,err:=net.Listen(SERVER_NETWORK,SERVER_ADDRESS)
	if err!=nil{
		printServerLog("Listen Error: %s",err)
		return
	}
	defer listener.Close()
	printServerLog("Got listener for the server. (local address:%s)",listener.Addr())
	for {
		conn,err:=listener.Accept()
		if err!=nil{
			printServerLog("Established a connection with a client application. (remote addr: %s)",conn.RemoteAddr())
		}
		handleConn(conn)
	}
}

func handleConn(conn net.Conn) {
	defer func() {
		conn.Close()
		wg.Done()
	}()
	for {
		conn.SetReadDeadline(time.Now().Add(10*time.Second))
		strReq,err:=read(conn)
		if err!=nil{
			if err==io.EOF{
				printServerLog("The connection is closed by another side.")
			}else {
				printServerLog("Read error: %s",err)
			}
			break
		}
		printServerLog("Received request: %s.",strReq)
		intReq,err:=strToInt32(strReq)
		if err!=nil{
			n,err:=write(conn,err.Error())
			printServerLog("Sent error message (written %d bytes): %s.",n,err)
			continue
		}
		floatResp:=cbrt(intReq)
		respMsg:=fmt.Sprintf("The cube root of %d is %f.",intReq,floatResp)
		n,err:=write(conn,respMsg)
		if err!=nil{
			printServerLog("Write error: %s",err.Error())
		}
		printServerLog("Sent response (written %d bytes): %s.",n,respMsg)
	}
}

func clientGo(id int) {
	defer wg.Done()
	conn,err:=net.DialTimeout(SERVER_NETWORK,SERVER_ADDRESS,2*time.Second)
	if err!=nil{
		printClientLog(id,"Dial error: %s",err)
		return
	}
	defer conn.Close()
	printClientLog(id,"Connected to server. (remote address: %s, local address: %s)",conn.RemoteAddr(),conn.LocalAddr())
	time.Sleep(200*time.Millisecond)
	requestNumber:=5
	conn.SetDeadline(time.Now().Add(5*time.Millisecond))
	for i:=0;i<requestNumber;i++{
		req:=rand.Int31()
		n,err:=write(conn,fmt.Sprintf("%d",req))
		if err!=nil{
			printClientLog(id,"Write error: %s",err)
			continue
		}
		printClientLog(id,"Sent request (written %d bytes): %d.",n,req)
	}
	for j:=0;j<requestNumber;j++{
		strResp,err:=read(conn)
		if err!=nil{
			if err==io.EOF{
				printClientLog(id,"The connection is closed by another side.")
			}else{
				printClientLog(id,"Read error: %s",err.Error())
			}
			break
		}
		printClientLog(id,"Received response: %s.",strResp)
	}
}

func main() {
	wg.Add(2)
	go serverGo()
	time.Sleep(500*time.Millisecond)
	go clientGo(1)
	wg.Wait()
}
